// [GzF]Chams  by Bmthrules
if SERVER then return end

local BM = {}

do
	local hooks = {}
	local created = {}
	local function CallHook(self, name, args)
		if !hooks[name] then return end
		for funcName, _ in pairs(hooks[name]) do
			local func = self[funcName]
			if func then
				local ok, err = pcall(func, self, unpack(args or {}))
				if !ok then
					ErrorNoHalt(err .. "\n")
				elseif err then
					return err
				end
			end
		end
	end
	local function AddHook(self, name, funcName)
		// If we haven't got a hook for this yet, make one with a random name and store it.
		// This is so anti-cheats can't detect by hook name, and so we can remove them later.
		if !created[name] then
			local random = ""
			for i = 1, math.random(4, 10) do
				local c = math.random(65, 116)
				if c >= 91 && c <= 96 then c = c + 6 end
				random = random .. string.char(c)
			end
				
			hook.Add(name, random, function(...) CallHook(self, name, {...}) end)
			created[name] = random
		end
		
		hooks[name] = hooks[name] or {}
		hooks[name][funcName] = true
	end
	
	// Don't let other scripts remove our hooks.
	local oldRemove = hook.Remove
	function hook.Remove(name, unique)
		if created[name] == unique then return end
		oldRemove(name, unique)
	end
	
	// Removes all hooks, useful if reloading the script.
	local function RemoveHooks()
		for hookName, unique in pairs(created) do
			oldRemove(hookName, unique)
		end
	end
	
	// Add copies the script can access.
	BM.AddHook = AddHook
	BM.CallHook = CallHook
	BM.RemoveHooks = RemoveHooks
end

concommand.Add("gzfchams_reload", function()
	BM:CallHook("Shutdown")
	print("Removing hooks...")
	BM:RemoveHooks()
	
	local info = debug.getinfo(1, "S")
	if info && info.short_src then
		print("Reloading (" .. info.short_src .. ")...")
		include(info.short_src)
	else
		
	end
end)
print("[GzF]Chams loaded....HA bitch i see you.")

do
	local settings = {}
	local function Settingvar(self, name)
		return (self.SettingPrefix or "") .. string.lower(name)
	end
	
	local function RandomName()
		local random = ""
		for i = 1, math.random(4, 10) do
			local c = math.random(65, 116)
			if c >= 91 && c <= 96 then c = c + 6 end
			random = random .. string.char(c)
		end
		return random
	end
	
	local function SetSetting(name, _, new)
		if !settings[name] then return end
		local info = settings[name]
		
		if info.Type == "number" then
			new = tonumber(new)
		elseif info.Type == "boolean" then
			new = (tonumber(new) or 0) > 0
		end
		
		info.value = new
	end
	
	local function CreateSetting(self, name, desc, default, misc)
		local cvar = Settingvar(self, name)
		local info = {Name = name, Desc = desc, Cvar = cvar, Type = type(default), value = default}
		
		for k, v in pairs(misc or {}) do
			if !info[k] then info[k] = v end
		end
		
		// Convert default from boolean to number.
		if type(default) == "boolean" then
			default = default and 1 or 0
		end
		
		if !settings[cvar] then
			local tab = cvars.GetConvarCallbacks(cvar)
			if !tab then
				cvars.AddChangeCallback(cvar, function() end)
				tab = cvars.GetConvarCallbacks(cvar)
			end
			
			while true do
				local name = RandomName()
				if !tab[name] then
					tab[name] = SetSetting
					info.Callback = name
					break
				end
			end
		end
		
		settings[cvar] = info
		settings[#settings + 1] = info
		
		// Create the convar.
		CreateClientConvar(cvar, default, (info.Save != false), false)
		SetSetting(cvar, _, GetConvarString(cvar))
	end
	local function GetSetting(self, name)
		local cvar = Settingvar(self, name)
		if !settings[cvar] then return end
		return settings[cvar].value
	end
	local function Shutdown()
		print("Removing settings callbacks...")
		for _, info in ipairs(settings) do
			if info.cvar && info.Callback then
				local tab = cvars.GetConvarCallbacks(info.cvar)
				if tab then
					tab[info.Callback] = nil
				end
			end
		end
	end
	local function SettingsList()
		return table.Copy(settings)
	end
	local function BuildMenu(self, panel)
		for _, info in ipairs(settings) do
			if info.Show != false then
				if info.MultiChoice then
					local m = panel:MultiChoice(info.Desc or info.cvar, info.cvar)
					for k, v in pairs(info.MultiChoice) do
						m:AddChoice(k, v)
					end
				elseif info.Type == "number" then
					panel:NumSlider(info.Desc or info.cvar, info.cvar, info.Min or -1, info.Max or -1, info.Places or 0)
				elseif info.Type == "boolean" then
					panel:CheckBox(info.Desc or info.cvar, info.cvar)
				elseif info.Type == "string" then
					panel:TextEntry(info.Desc or info.cvar, info.cvar)
				end
			end
		end
	end
	
	BM.SettingPrefix = "gzfchams_"
	BM.CreateSetting = CreateSetting
	BM.Setting = GetSetting
	BM.SettingsList = SettingsList
	BM.BuildMenu = BuildMenu
	
	BM.SettingsShutdown = Shutdown
	BM:AddHook("Shutdown", "SettingsShutdown")
end

BM:CreateSetting("updatedelay", "Target update delay", 1, {Min = 0, Max = 5})
BM:CreateSetting("enabled", "Enabled", true)
BM:CreateSetting("fullbright", "Fullbright", true)
BM:CreateSetting("nodraw", "NoDraw entities to increase performance", false)
BM:CreateSetting("showfriendlies", "Draw members of your own team", true)
BM:CreateSetting("heldweapons", "Draw held weapons", true)
BM:CreateSetting("weapons", "Draw dropped weapons", true)
BM:CreateSetting("items", "Draw items", true)
BM:CreateSetting("ragdolls", "Draw ragdolls", true)
BM:CreateSetting("dangerous", "Draw dangerous items", true)

// ##################################################
// Targeting
// ##################################################

BM.DeathSequences = {
	["models/barnacle.mdl"] = {4, 15};
	["models/antlion_guard.mdl"] = {44};
	["models/hunter.mdl"] = {124, 125, 126, 127, 128};
}
BM.DangerousItems = {
	["npc_grenade_frag"] = true; // Normal grenades.
	["npc_satchel"] = true; // SLAM mines.
	["npc_tripmine"] = true; // SLAM tripwires.
	["rpg_missile"] = true; // RPG rockets.
	["crossbow_bolt"] = true; // Crossbow bolts...
	["grenade_ar2"] = true; // SMG grenades.
}
BM.EnemyNPCs = {
	["npc_headcrab"] = true;
	["npc_antlionguard"] = true;
	["npc_zombie"] = true;
	["npc_zombine"] = true;
	["npc_antlion_worker"] = true;
	["npc_metropolice"] = true;
	["npc_fastzombie_torso"] = true;
	["npc_combine_s"] = true;
	["npc_zombie_torso"] = true;
	["npc_rollermine"] = true;
	["npc_turret_floor"] = true;
	["npc_barnacle"] = true;
	["npc_poisonzombie"] = true;
	["npc_manhack"] = true;
	["npc_antlion"] = true;
	["npc_headcrab_black"] = true;
	["npc_headcrab_poison"] = true;
	["npc_fastzombie"] = true;
	["npc_combine_s"] = true;
	["npc_hunter"] = true;
	["npc_headcrab_fast"] = true;
	["npc_combine_s"] = true;
	["npc_cscanner"] = true;
	["npc_strider"] = true;
}

function BM:TargetInfo(ent)
	if !ValidEntity(ent) then return end
	local ply = LocalPlayer()
	if !ValidEntity(ply) then return end	
		
	local class = ent:GetClass()
	
	if ent:IsPlayer() then
		if ent:Alive() == false then return end
		if ply:Team() == ent:Team() && !self:Setting("showfriendlies") then return end
		
		local col = team.GetColor(ent:Team())
		
		local name = team.GetName(ent:Team())
		if name == "Spectator" then
			col.a = 64
		end
		
		return true, col
	end
	if ent:IsNPC() then
		if ent:GetMoveType() == MOVETYPE_NONE then return end
		if ent.IsDead then return end

		local model = string.lower(ent:GetModel() or "")
		if self.DeathSequences[model] then
			if table.Hasvalue(self.DeathSequences[model], ent:GetSequence()) then return end
		end
		
		if self.EnemyNPCs[class] then
			return true, Color(221, 16, 16)
		else
			return true, Color(17, 74, 206)
		end
	end
	if ent:IsWeapon() then
		if ValidEntity(ent:GetOwner()) then
			if self:Setting("heldweapons") then
				return self:TargetInfo(ent:GetOwner()), nil
			end
		else
			return self:Setting("weapons"), nil, true
		end
		
		return true
	end
	
	if self:Setting("items") then
		if string.find(class, "item") then
			return true
		end
	end
	
	if self:Setting("ragdolls") then
		if class == "prop_ragdoll" || class == "class C_ClientRagdoll" then
			return true, Color(56, 49, 49)
		end
	end
	
	if self:Setting("dangerous") then
		if self.DangerousItems[class] then
			return true, Color(234, 31, 21)
		end
	end
end
function BM:TargetCreated(ent)
	if !ValidEntity(ent) then return end
	
	local pass, colour, prop = self:TargetInfo(ent)
	if pass == true then
		self:AddTarget(ent, colour, prop)
	elseif self:IsNoDraw(ent:GetOwner()) then
		self:SetNoDraw(ent, false)
	end
	
	if ent:GetClass() == "raggib" then
		local owner = ent:GetOwner()
		if ValidEntity(owner) && owner:GetModel() != "models/zombie/classic_torso.mdl" then // They're a torso, not dead.
			owner.IsDead = true
		end
	end
end
BM:AddHook("OnEntityCreated", "TargetCreated")

BM.Targets = {}
BM.LastFindTargets = 0
local targets
function BM:FindTargets()
	if !self:Enabled() then return end
	
	if CurTime() < self.LastFindTargets + self:Setting("updatedelay") then return end
	self.LastFindTargets = CurTime()
		
	targets = {}
	for _, ent in pairs(ents.GetAll()) do
		local pass, colour, prop = self:TargetInfo(ent)
		if pass == true then
			targets[ent] = {colour or Color(255, 255, 255), prop == true}
		end
	end
	
	for targ, _ in pairs(self.Targets) do
		if ValidEntity(targ) && !targets[targ] then
			self:RemoveTarget(ent)
		end
	end
	
	self.Targets = targets
end
BM:AddHook("Think", "FindTargets")

function BM:AddTarget(ent, colour, prop)
	self.Targets[ent] = {colour or Color(255, 255, 255), prop == true}
end
function BM:RemoveTarget(ent)
	if !ent then return end
	if self:UseNoDraw() then
		self:SetNoDraw(ent, false)
	end
	self.Targets[ent] = nil
end

function BM:GetTargets()
	return self.Targets
end

// ##################################################
// Materials
// ##################################################

BM.Materials = {}
BM.MaterialNames = {}

BM:CreateSetting("material", "Material", "wireframe", {MultiChoice = BM.MaterialNames})

function BM:GetMaterial()
	local name = string.lower(self:Setting("material"))
	return self.Materials[name]
end
function BM:AddMaterial(name, base, shader)
	if !shader then shader = "VertexLitGeneric" end
	name = string.lower(name)
	
	local mat = CreateMaterial("viewall_" .. name, shader, {
		["$basetexture"] = base,
		["$ignorez"] = 1
	})
	self.Materials[name] = mat
	self.MaterialNames[name] = name
end
function BM:GetMaterialList()
	return self.Materials
end

BM:AddMaterial("solid", "models/debug/debugwhite")
BM:AddMaterial("wireframe", "models/wireframe", "Wireframe")

// ##################################################
// Rendering
// ##################################################

local targets, colour, Draw
function BM:RenderTargets()
	if !self:Enabled() then return end
	
	targets = self:GetTargets()
	//if #targets == 0 then return end
	
	local mat = self:GetMaterial(matName)
	
	// NoDraw to save drawing, and weed out invalid entities.
	local pass
	for ent, info in pairs(targets) do
		pass = self:TargetInfo(ent)
		if pass then
			if self:UseNoDraw() then
				self:SetNoDraw(ent, true)
			end
		else
			self:RemoveTarget(ent)
		end
	end
	
	local fullbright = self:Setting("fullbright")
	if fullbright then
		render.SuppressEngineLighting(true)
	end
	
	if !ValidEntity(self.Prop) then
		self.Prop = ents.Create("prop_physics")
		self.Prop:SetNoDraw(true)
		self.Prop:Spawn()
	end
	
	function Draw(ent, r, g, b, a, prop)
		render.SetColorModulation(r / 255, g / 255, b / 255)
		render.SetBlend(a / 255)
		
		if prop then
			self.Prop:SetModel("models/error.mdl")
			self.Prop:SetModel(ent:GetModel())
			self.Prop:SetPos(ent:GetPos())
			self.Prop:SetAngles(ent:GetAngles())
			
			self.Prop:DrawModel()
		else
			ent:DrawModel()
		end
	end
	
	cam.Start3D(EyePos(), EyeAngles())
	pcall(function()
		if mat != nil then
			SetMaterialOverride(mat)
			for ent, info in pairs(targets) do
				Draw(ent, info[1]["r"], info[1]["g"], info[1]["b"], info[1]["a"], info[2])
			end
			SetMaterialOverride(nil)
		end
		local r, g, b
		for ent, info in pairs(targets) do
			r, g, b = ent:GetColor()
			Draw(ent, r, g, b, info[1]["a"], info[2])
		end
	end)
	cam.End3D()
	
	render.SetBlend(1)
	
	if fullbright then
		render.SuppressEngineLighting(false)
	end
end
BM:AddHook("RenderScreenspaceEffects", "RenderTargets")

function BM:Enabled()
	return self:Setting("enabled")
end

BM.NoDraw = {}
function BM:SetNoDraw(ent, bool)
	if !ValidEntity(ent) then return end
	if bool then
		ent:SetRenderMode(RENDERMODE_NONE)
		self.NoDraw[ent] = true
	else
		ent:SetRenderMode(RENDERMODE_NORMAL)
		self.NoDraw[ent] = nil
	end
end
function BM:IsNoDraw(ent)
	return self.NoDraw[ent] == true
end
function BM:UseNoDraw()
	return self:Setting("nodraw")
end

function BM:OpenMenu()
	local w, h = ScrW() / 3, ScrH() / 2

	local menu = vgui.Create("DFrame")
	menu:SetTitle("[GzF]Chams by Bmthrules")
	menu:SetSize(w, h)
	menu:Center()
	menu:MakePopup()

	local scroll = vgui.Create("DPanelList", menu)
	scroll:SetPos(5, 25)
	scroll:SetSize(w - 10, h - 30)
	scroll:EnableVerticalScrollbar()

	local form = vgui.Create("DForm", menu)
	form:SetName("")
	form.Paint = function() end
	scroll:AddItem(form)

	self:BuildMenu(form)

	if BM.Menu then BM.Menu:Remove() end
	BM.Menu = menu
end
concommand.Add("gzfchams_menu", function() BM:OpenMenu() end)

function BM:RegisterMenu()
	spawnmenu.AddToolMenuOption("[GzF]Hacks", "[Gzf]Chams by Bmthrules", "Chams", "Cham settings", "", "", function(p) self:BuildMenu(p) end)
end
BM:AddHook("PopulateToolMenu", "RegisterMenu")



